<?php

declare(strict_types=1);

namespace app\common\util;

/**
 * Zip 文件操作类 - 只处理zip
 */
class ZipHandle
{
  protected $zip;
  protected $root_path;
  protected $ignored_files;

  public function __construct()
  {
    $this->zip = new \ZipArchive;
  }

  /**
   * 解压到指定文件夹
   * @param string $origin_file 源文件
   * @param string $path 目标路径
   * @return bool 成功true
   */
  public function unzip($origin_file, $path)
  {
    if ($this->zip->open($origin_file) === true) {
      $temp = @fopen($origin_file, "rb");
      $bytes = fread($temp, 15); //取前15字节
      fclose($temp);
      if (true === $this->getTypeList($bytes)) {
        $result = $this->zip->extractTo($path);
        $this->zip->close();
        return $result;
      } else {
        return false;
      }
    }
    return false;
  }

  /**
   * 创建压缩文件
   * @param string $origin_file 源文件路径
   * @param string $folder 源文件夹路径
   * @param array  $ignored 忽略文件列表
   * @return bool 压缩包 成功=true
   */
  public function zip($origin_file, $folder, $ignored = null)
  {
    if (is_array($ignored)) {
      $this->ignored_files = $ignored;
    } else {
      $this->ignored_files = $ignored ? array($ignored) : array();
    }
    if ($this->zip->open($origin_file, \ZIPARCHIVE::CREATE) !== true) {
      throw new \Exception("cannot open <$origin_file>\n");
    }
    $folder = substr($folder, -1) == '/' ? substr($folder, 0, strlen($folder) - 1) : $folder;
    if (strstr($folder, '/')) {
      $this->root_path = substr($folder, 0, strrpos($folder, '/') + 1);
      $folder = substr($folder, strrpos($folder, '/') + 1);
    }
    $this->createZip($folder);
    return $this->zip->close();
  }

  /**
   * 递归添加文件到压缩包
   * @param string $folder 添加到压缩包的文件夹路径
   * @param string $parent 添加到压缩包的文件夹上级路径
   * @return void
   */
  private function createZip($folder, $parent = null)
  {
    $full_path = $this->root_path . $parent . $folder;
    $zip_path = $parent . $folder;
    $this->zip->addEmptyDir($zip_path);
    $dir = new \DirectoryIterator($full_path);
    foreach ($dir as $file) {
      if (!$file->isDot()) {
        $filename = $file->getFilename();
        if (!in_array($filename, $this->ignored_files)) {
          if ($file->isDir()) {
            $this->createZip($filename, $zip_path . '/');
          } else {
            //第二个参数是重命名文件名,带上路径就可以改变当前文件在压缩包里面的路径.
            $this->zip->addFile($full_path . '/' . $filename, $zip_path . '/' . $filename);
          }
        }
      }
    }
  }


  /**
   * 读取压缩包文件与目录列表
   * @param string $origin_file 压缩包文件
   * @return array 文件与目录列表
   */
  public function fileList($origin_file)
  {
    $file_dir_list = array();
    $file_list = array();
    if ($this->zip->open($origin_file) == true) {
      for ($i = 0; $i < $this->zip->numFiles; $i++) {
        $numfiles = $this->zip->getNameIndex($i);
        if (preg_match('/\/$/i', $numfiles)) {
          $file_dir_list[] = $numfiles;
        } else {
          $file_list[] = $numfiles;
        }
      }
    }
    return array('files' => $file_list, 'dirs' => $file_dir_list);
  }

  /**
   * 得到文件头与文件类型映射表
   * @param $bin string 文件的二进制前一段字符
   * @return boolean
   */
  private function getTypeList($bin)
  {
    $array = array(
      array("504B0304", "zip")
    );
    foreach ($array as $v) {
      $blen = strlen(pack("H*", $v[0])); //得到文件头标记字节数
      $tbin = substr($bin, 0, intval($blen)); //需要比较文件头长度
      if (strtolower($v[0]) == strtolower(array_shift(unpack("H*", $tbin)))) {
        return true;
      }
    }
    return false;
  }
}

/******** ziparchive 可选参数 *******/
/*
1.ZipArchive::addEmptyDir
添加一个新的文件目录
2.ZipArchive::addFile 
将文件添加到指定zip压缩包中。
3.ZipArchive::addFromString 
添加的文件同时将内容添加进去
4.ZipArchive::close 
关闭ziparchive
5.ZipArchive::extractTo 
将压缩包解压
6.ZipArchive::open
打开一个zip压缩包
7.ZipArchive::getStatusString
返回压缩时的状态内容，包括错误信息，压缩信息等等
8.ZipArchive::deleteIndex
删除压缩包中的某一个文件，如：deleteIndex(0)删除第一个文件
9.ZipArchive::deleteName
删除压缩包中的某一个文件名称，同时也将文件删除。
......
*/